"""
State Manager for ArXiv Scraper Service

Manages persistent state for tracking scraping progress,
last successful runs, and recovery information.
"""

import json
import logging
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional, Dict, Any
import fcntl


class StateManager:
    """
    State manager for persistent scraper state.
    
    Features:
    - File-based state persistence
    - Last successful run tracking
    - Recovery information storage
    - Atomic file operations with locking
    """
    
    def __init__(self, state_file_path: str):
        """Initialize state manager.
        
        Args:
            state_file_path: Path to state file
        """
        self.state_file_path = Path(state_file_path)
        self.logger = logging.getLogger(__name__)
        
        # Ensure state directory exists
        self.state_file_path.parent.mkdir(parents=True, exist_ok=True)
        
        # Initialize state file if it doesn't exist
        if not self.state_file_path.exists():
            self._initialize_state_file()
        
        self.logger.info(f"State manager initialized: {self.state_file_path}")
    
    def _initialize_state_file(self):
        """Initialize empty state file."""
        initial_state = {
            'version': '1.0.0',
            'created_at': datetime.now(timezone.utc).isoformat(),
            'last_successful_run': None,
            'last_failed_run': None,
            'total_runs': 0,
            'total_papers_processed': 0,
            'resumption_token': None,
            'metrics': {},
            'configuration': {}
        }
        
        self._write_state(initial_state)
        self.logger.info("State file initialized")
    
    def _read_state(self) -> Dict[str, Any]:
        """Read state from file with file locking."""
        try:
            with open(self.state_file_path, 'r', encoding='utf-8') as f:
                # Acquire shared lock for reading
                fcntl.flock(f.fileno(), fcntl.LOCK_SH)
                try:
                    state = json.load(f)
                finally:
                    fcntl.flock(f.fileno(), fcntl.LOCK_UN)
            
            return state
            
        except (FileNotFoundError, json.JSONDecodeError) as e:
            self.logger.warning(f"Error reading state file, reinitializing: {e}")
            self._initialize_state_file()
            return self._read_state()
        except Exception as e:
            self.logger.error(f"Unexpected error reading state: {e}")
            raise
    
    def _write_state(self, state: Dict[str, Any]):
        """Write state to file with atomic operations and file locking."""
        try:
            # Write to temporary file first
            temp_file = self.state_file_path.with_suffix('.tmp')
            
            with open(temp_file, 'w', encoding='utf-8') as f:
                # Acquire exclusive lock for writing
                fcntl.flock(f.fileno(), fcntl.LOCK_EX)
                try:
                    json.dump(state, f, indent=2, ensure_ascii=False, default=str)
                    f.flush()
                finally:
                    fcntl.flock(f.fileno(), fcntl.LOCK_UN)
            
            # Atomically replace the original file
            temp_file.replace(self.state_file_path)
            
        except Exception as e:
            self.logger.error(f"Error writing state file: {e}")
            # Clean up temporary file if it exists
            if temp_file.exists():
                temp_file.unlink()
            raise
    
    def get_last_successful_run(self) -> Optional[datetime]:
        """Get timestamp of last successful run."""
        state = self._read_state()
        
        last_run_str = state.get('last_successful_run')
        if last_run_str:
            try:
                return datetime.fromisoformat(last_run_str.replace('Z', '+00:00'))
            except ValueError as e:
                self.logger.warning(f"Invalid timestamp in state: {last_run_str}, error: {e}")
        
        return None
    
    def update_last_successful_run(self, timestamp: datetime):
        """Update timestamp of last successful run."""
        state = self._read_state()
        
        state['last_successful_run'] = timestamp.isoformat()
        state['total_runs'] = state.get('total_runs', 0) + 1
        state['updated_at'] = datetime.now(timezone.utc).isoformat()
        
        self._write_state(state)
        self.logger.info(f"Updated last successful run: {timestamp}")
    
    def get_last_failed_run(self) -> Optional[datetime]:
        """Get timestamp of last failed run."""
        state = self._read_state()
        
        last_failed_str = state.get('last_failed_run')
        if last_failed_str:
            try:
                return datetime.fromisoformat(last_failed_str.replace('Z', '+00:00'))
            except ValueError as e:
                self.logger.warning(f"Invalid timestamp in state: {last_failed_str}, error: {e}")
        
        return None
    
    def update_last_failed_run(self, timestamp: datetime, error_info: Optional[str] = None):
        """Update timestamp and info of last failed run."""
        state = self._read_state()
        
        state['last_failed_run'] = timestamp.isoformat()
        if error_info:
            state['last_error'] = error_info
        state['updated_at'] = datetime.now(timezone.utc).isoformat()
        
        self._write_state(state)
        self.logger.info(f"Updated last failed run: {timestamp}")
    
    def get_resumption_token(self) -> Optional[str]:
        """Get stored resumption token for recovery."""
        state = self._read_state()
        return state.get('resumption_token')
    
    def store_resumption_token(self, token: str):
        """Store resumption token for recovery."""
        state = self._read_state()
        
        state['resumption_token'] = token
        state['updated_at'] = datetime.now(timezone.utc).isoformat()
        
        self._write_state(state)
        self.logger.debug(f"Stored resumption token: {token[:50]}...")
    
    def clear_resumption_token(self):
        """Clear stored resumption token."""
        state = self._read_state()
        
        if 'resumption_token' in state:
            del state['resumption_token']
            state['updated_at'] = datetime.now(timezone.utc).isoformat()
            self._write_state(state)
            self.logger.debug("Cleared resumption token")
    
    def update_metrics(self, metrics: Dict[str, Any]):
        """Update stored metrics."""
        state = self._read_state()
        
        # Merge with existing metrics
        current_metrics = state.get('metrics', {})
        current_metrics.update(metrics)
        
        state['metrics'] = current_metrics
        state['updated_at'] = datetime.now(timezone.utc).isoformat()
        
        self._write_state(state)
    
    def get_metrics(self) -> Dict[str, Any]:
        """Get stored metrics."""
        state = self._read_state()
        return state.get('metrics', {})
    
    def increment_papers_processed(self, count: int):
        """Increment total papers processed counter."""
        state = self._read_state()
        
        state['total_papers_processed'] = state.get('total_papers_processed', 0) + count
        state['updated_at'] = datetime.now(timezone.utc).isoformat()
        
        self._write_state(state)
    
    def get_total_papers_processed(self) -> int:
        """Get total papers processed across all runs."""
        state = self._read_state()
        return state.get('total_papers_processed', 0)
    
    def get_run_statistics(self) -> Dict[str, Any]:
        """Get comprehensive run statistics."""
        state = self._read_state()
        
        return {
            'total_runs': state.get('total_runs', 0),
            'total_papers_processed': state.get('total_papers_processed', 0),
            'last_successful_run': state.get('last_successful_run'),
            'last_failed_run': state.get('last_failed_run'),
            'last_error': state.get('last_error'),
            'has_resumption_token': bool(state.get('resumption_token')),
            'created_at': state.get('created_at'),
            'updated_at': state.get('updated_at')
        }
    
    def store_configuration(self, config: Dict[str, Any]):
        """Store configuration snapshot."""
        state = self._read_state()
        
        # Only store relevant configuration parts (no secrets)
        safe_config = {
            'rate_limit_delay': config.get('scraping', {}).get('rate_limit_delay'),
            'batch_size': config.get('processing', {}).get('batch_size'),
            'max_retries': config.get('scraping', {}).get('max_retries'),
            'subjects': config.get('arxiv', {}).get('subjects', [])
        }
        
        state['configuration'] = safe_config
        state['updated_at'] = datetime.now(timezone.utc).isoformat()
        
        self._write_state(state)
    
    def get_configuration(self) -> Dict[str, Any]:
        """Get stored configuration."""
        state = self._read_state()
        return state.get('configuration', {})
    
    def clear_state(self):
        """Clear all state (for full resync)."""
        self.logger.info("Clearing all state for full resync")
        
        state = self._read_state()
        
        # Keep creation info and version, clear everything else
        cleared_state = {
            'version': state.get('version', '1.0.0'),
            'created_at': state.get('created_at'),
            'cleared_at': datetime.now(timezone.utc).isoformat(),
            'last_successful_run': None,
            'last_failed_run': None,
            'total_runs': 0,
            'total_papers_processed': 0,
            'resumption_token': None,
            'metrics': {},
            'configuration': state.get('configuration', {})
        }
        
        self._write_state(cleared_state)
        self.logger.info("State cleared successfully")
    
    def backup_state(self, backup_path: Optional[str] = None) -> str:
        """Create backup of current state."""
        if backup_path is None:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            backup_path = f"{self.state_file_path}.backup_{timestamp}"
        
        backup_path = Path(backup_path)
        
        try:
            state = self._read_state()
            
            with open(backup_path, 'w', encoding='utf-8') as f:
                json.dump(state, f, indent=2, ensure_ascii=False, default=str)
            
            self.logger.info(f"State backed up to: {backup_path}")
            return str(backup_path)
            
        except Exception as e:
            self.logger.error(f"Failed to backup state: {e}")
            raise
    
    def restore_state(self, backup_path: str):
        """Restore state from backup."""
        backup_path = Path(backup_path)
        
        if not backup_path.exists():
            raise FileNotFoundError(f"Backup file not found: {backup_path}")
        
        try:
            with open(backup_path, 'r', encoding='utf-8') as f:
                backup_state = json.load(f)
            
            # Add restore information
            backup_state['restored_at'] = datetime.now(timezone.utc).isoformat()
            backup_state['restored_from'] = str(backup_path)
            
            self._write_state(backup_state)
            
            self.logger.info(f"State restored from: {backup_path}")
            
        except Exception as e:
            self.logger.error(f"Failed to restore state: {e}")
            raise
    
    def health_check(self) -> bool:
        """Perform health check of state manager."""
        try:
            # Test read/write operations
            state = self._read_state()
            
            # Test write by updating a timestamp
            state['health_check'] = datetime.now(timezone.utc).isoformat()
            self._write_state(state)
            
            # Verify the write
            updated_state = self._read_state()
            
            return 'health_check' in updated_state
            
        except Exception as e:
            self.logger.error(f"State manager health check failed: {e}")
            return False
    
    def get_state_file_info(self) -> Dict[str, Any]:
        """Get information about the state file."""
        try:
            if self.state_file_path.exists():
                stat = self.state_file_path.stat()
                return {
                    'path': str(self.state_file_path),
                    'size_bytes': stat.st_size,
                    'modified_time': datetime.fromtimestamp(stat.st_mtime, timezone.utc).isoformat(),
                    'exists': True
                }
            else:
                return {
                    'path': str(self.state_file_path),
                    'exists': False
                }
        except Exception as e:
            self.logger.error(f"Error getting state file info: {e}")
            return {'error': str(e)}